package com.liuguang.realgateway.filter;

import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.liuguang.api.redis.RedisService;
import com.liuguang.common.constans.CommonConstans;
import com.liuguang.common.enums.RedisPreFixEnum;
import com.liuguang.common.pojo.CommonResult;
import com.liuguang.common.utils.JwtUtil;
import com.liuguang.realgateway.utils.GatewayUtil;
import org.apache.dubbo.config.annotation.DubboReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.*;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName: MyGolableFilter
 * @Description： 自定义全局过滤器，进行jwt 权限验证，
 * 微服务情况下，用户的注册和登录在用户模块，但是其他的接口请求要在网关的地方进行鉴权，
 * 鉴权成功后请求才会到达相关的微服务，
 * <p>
 * TODO：
 * 1、既然在这个过滤器可以进行鉴权，那么admin 的springSecurity 框架还有什么意义？
 * 在登录的时候进行验证作用，再次访问接口的时候在这个filter进行
 * 随之而来的：是否有权限访问接口？如何设计？角色表，角色所对的路径，保存每个路径对应的角色信息，看是否包含
 * 2、
 * @Author: wanghr
 * Date: 3:50 PM 2020/10/26
 * Version: 1.0
 */
@Configuration
public class JwtAuthGlobalFilter implements GlobalFilter {
    private static final Logger logger = LoggerFactory.getLogger(JwtAuthGlobalFilter.class);
    @Value("${passpath.urls}")
    private String passPath;

    @DubboReference
    private RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        logger.info("进入到自定义的过滤器");
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //判断是不需验证的路径，直接向下执行通过
        String[] paths = passPath.split(",");
        ArrayList<String> pathList = new ArrayList<>(Arrays.asList(paths));
        String uri = request.getURI().getPath();
//        if (pathList.contains(uri)) {
        if (true) {
            logger.info("请求的路径是不需验证的路径");

            int size = FlowRuleManager.getRules().size();
            System.out.println("FlowRuleManager的个数："+size);
            if (size > 0) {
                FlowRuleManager.getRules().forEach(flowRule -> {
                    logger.info("获取rules:{}",flowRule);
                });
            }
            int size1 = GatewayRuleManager.getRules().size();
            System.out.println("GatewayRuleManager的个数："+size1);
            if (size1 > 0) {
                GatewayRuleManager.getRules().forEach(gatewayFlowRule -> {
                    logger.info("gatewayFlowRule:{}", gatewayFlowRule);
                });
            }

            return chain.filter(exchange);
        }
        //获取token
        String token = getToken(request);
        logger.info("获取token：{}", token);
        if (!StringUtils.isEmpty(token)) {
            //解析token

            //不能在工具类中获取，此时获取的key与生成token时加密的不一样
            String jwtClaimKeyFromRedis = redisService.get(RedisPreFixEnum.JWT_CLAIM_KEY.getValue()).getData();
            logger.info("jwtClaimKeyFromRedis:{}", jwtClaimKeyFromRedis);

            if (JwtUtil.validateTokenWithKey(token, jwtClaimKeyFromRedis)) {
                praseToken(token, jwtClaimKeyFromRedis, request);
                //todo：验证是否有权限访问接口,在后面的服务中自己去验证
            } else {
                logger.info("jwtToken解析失败");
                String msg = CommonConstans.TOKEN_PRASE_FAIL;
                return GatewayUtil.feedbackFriendly(response, msg);
            }

        } else {
            logger.info("token为null");
            String msg = CommonConstans.TOKEN_NULL;
            return GatewayUtil.feedbackFriendly(response, msg);
        }
        return chain.filter(exchange);
    }


    private void praseToken(String token, String jwtClaimKeyFromRedis, ServerHttpRequest request) {
        //获取 用户信息，包括权限信息。
        // 如果用户登录后直接将用户信息保存到redis 中，而不是保存到token中，
        // 既省去了token携带，只需保存个用户的唯一凭证
        // ，又能够在各个服务中直接获取，弊端在哪？
        //唯一凭证被盗用，那么会出现问题，解决了csrf 问题，但是直接返回token就行，不用非要把用户信息封装进去
        Map parseToken = JwtUtil.parseTokenWithKey(token, jwtClaimKeyFromRedis);
        logger.info("解析后token：{}", parseToken);

        Date expiredTime = (Date) parseToken.get("expiredTime");
        System.out.println("expiredTime = " + expiredTime);

        List<String> list = (List<String>) parseToken.get("auth");
        String username = (String) parseToken.get("username");
        long time = expiredTime.getTime() - System.currentTimeMillis();
        System.out.println("time = " + time);

        if (time < 300000) {
            //未过期距离过期时间是否小于5分钟，小于该时间刷新token
            String newToken = JwtUtil.createToken(parseToken);
            //将新token 放到 response 中,
            // 这就意味着前端每次调用接口后都要更新一下保存的token信息？这样也太麻烦了
            //todo:
//                response.addHeader("newToken", newToken);
        }

        // redis 保存用户信息，方便其他微服务获取用户信息，此处主要是检查作用，因为用户保存时间为1天
        //可以设置 redis 用户信息的保存时间比token的时间长，就不会存在：token解析成功，但是redis 获取不到用户的信息
        //检查：在redis 获取用户信息，获取不到数据库获取添加到redis
        String key = RedisPreFixEnum.SAVE_USER_INFO.getValue() + username;
//        CommonResult<String> redisResult = redisService.get(key);
//        if (!CommonResultEnum.SUCCESS.getCode().equals(redisResult.getCode())) {
//            //在redis获取不到，数据库获取
//            logger.info("在redis未获取到用户：{}信息，数据库获取", username);
//        }
        //刷新时间
        redisService.expire(key, 24 * 60L, TimeUnit.MINUTES);
        //在请求中添加用户信息，后序就不用在解析token，直接到redis获取用户信息即可
        request.getHeaders().set("token", "");
        request.getHeaders().set("username", username);
    }

    private String getToken(ServerHttpRequest request) {
        //正常与前端约定好，只在一个地方取就行
        //在请求头获取 token
        String token = null;
        HttpHeaders headers = request.getHeaders();
        if (headers.containsKey("token")) {
            List<String> list = headers.get("token");
            if (!CollectionUtils.isEmpty(list) && list.size() == 1) {
                return token = list.get(0);
            }
        }
        //在请求参数获取 token
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        List<String> list = queryParams.get("token");
        if (!CollectionUtils.isEmpty(list) && list.size() == 1) {
            return token = list.get(0);
        }
        return token;
    }
}

